Add balance_before and balance_after to transactions#27
Conversation
Store running balance information in transaction metadata for audit trail, similar to banking apps that show balance after each transaction. - Add balance_after and balance_before accessors to Transaction model - Store both values in metadata during add_credits and deduct_credits - Add formatted_balance_after helper method - Add 27 comprehensive tests covering edge cases No migrations required - uses existing metadata JSON column. Fully backwards compatible - returns nil for old transactions. Closes #8 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code Review for PR #27: Add balance_before and balance_after to transactionsSummaryThis PR implements a banking-like running balance feature for transactions, adding balance_after and balance_before fields stored in transaction metadata. Overall, this is a well-designed and thoroughly tested implementation that follows the existing codebase patterns. StrengthsCode Quality
Design Decisions
Testing
Areas for Consideration1. Race Condition in Two-Write Pattern (Medium Priority)Location: lib/usage_credits/models/wallet.rb:187-190 and 287-290 The implementation uses two database writes. Between the first and second write, the transaction exists without balance metadata. If the second update! fails, transactions would be created without balance information. Recommendation: Consider computing balance upfront and including in initial create!, or add defensive backfill logic. 2. Performance Consideration (Low Priority)The current implementation calls credits method twice per transaction, which executes a complex SQL query each time. For high-volume operations, this adds 2x query overhead. Recommendation: Cache the balance calculation within the lock block, or use the balance column directly since it is kept in sync. 3. Metadata Merge Ordering (Low Priority)The code merges balance information into existing metadata. If custom metadata happens to include balance_before or balance_after keys, they would be overwritten silently. Recommendation: Consider namespacing system metadata or documenting that these keys are reserved. SecurityNo security concerns identified. Uses parameterized queries, proper escaping, and row-level locks. Test CoverageThe test suite is exceptionally comprehensive with 27 tests covering all major scenarios and edge cases. Suggested additions (optional):
DocumentationThe PR description is excellent with clear explanation of design decisions, trade-offs, safety analysis, and usage examples. Minor suggestion: Add a usage example to README.md showing the new feature in action. RecommendationAPPROVE with minor suggestions This is a high-quality PR with excellent testing and documentation. The two-write pattern is the only notable concern, but it is within acceptable risk since operations are within a lock, old transactions gracefully return nil, and the feature is additive. Great work on this feature! |
- Add comments explaining atomicity guarantee (with_lock ensures rollback) - Document that balance_before/balance_after keys are system-reserved - Add running balance documentation to README Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Addressing the review points: 1. Two-Write Pattern (Medium Priority)Good catch on the pattern, but this is actually zero risk rather than "acceptable risk": The
However, I agree the code could be clearer. I've added an explanatory comment in d9dcda6: # Store balance information in transaction metadata for audit trail.
# Note: This update! is in the same DB transaction as the create! above (via with_lock),
# so if this fails, the entire transaction rolls back - no orphaned records possible.2. Metadata Merge Ordering (Low Priority)The current behavior is intentional - we want to overwrite any user-supplied This follows the same pattern used elsewhere in the codebase (e.g., I've added a comment documenting this is intentional: # We intentionally overwrite any user-supplied balance_before/balance_after keys
# to ensure system-set values are authoritative. |
balance_before and balance_after to transactions
Implements the "balance after transaction" feature requested in #8 — adding running balance information to transactions, similar to how banking apps display balance after each transaction.
Key additions:
transaction.balance_after— the wallet balance immediately after this transactiontransaction.balance_before— the wallet balance immediately before this transactiontransaction.formatted_balance_after— formatted output using configured credit formatterWhy This Approach?
Goal
Add banking-like running balance to transaction history with:
metadataJSON columnnilgracefullyConstraints Considered
Design Decisions
Why store in metadata vs. a new column?
Why store BOTH balance_before AND balance_after?
Initially, I implemented
balance_beforeas calculated:balance_after - amount. This works 99% of the time, but breaks in edge cases:For a banking-like system handling money-equivalent assets, calculated values that can be wrong in edge cases are unacceptable. Explicit storage guarantees correctness.
Why update metadata after transaction creation (two writes)?
The balance can only be accurately computed AFTER the transaction exists (the
creditsmethod queries all transactions). Options considered:previous_balance ± amount) — Simpler formula but could diverge from actualcreditscalculation in edge caseswallet.balancecolumnChose option 2 for safety. The extra write is negligible and fully protected by the database transaction.
Safety Analysis
ACID Compliance
with_lock do...end— ifupdate!fails, entire transaction rolls backbalance_afteralways equalswallet.balanceat commit timeSELECT FOR UPDATElock prevents concurrent modificationsCallback Safety
Callbacks are executed inside
execute_safelywhich rescues all errors — callbacks can never break credit operations or cause partial commits.All Entry Points Covered
Audited every code path that creates transactions:
give_creditsadd_creditsspend_credits_ondeduct_creditsadd_creditsadd_creditsadd_creditsadd_creditsdeduct_creditsTest Coverage
Added 27 comprehensive tests covering:
give_credits,deduct_credits,spend_credits_onbalance_before + amount = balance_after)allow_negative_balance)Test Results: 700 tests, 1587 assertions, 0 failures, 0 errors
Usage Example
Files Changed
lib/usage_credits/models/transaction.rblib/usage_credits/models/wallet.rbtest/models/usage_credits/transaction_test.rbChecklist
nil)Closes #8
🤖 Generated with Claude Code